package com.atguigu.gmall.index.aspect;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.index.annotation.GmallCache;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
public class GmallCacheAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RBloomFilter bloomFilter;

    @Around("@annotation(com.atguigu.gmall.index.annotation.GmallCache)")
    public Object cacheAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        //获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();

        //获取目标方法对象
        Method method = signature.getMethod();

        //获取目标方法上的GmallCache注解对象
        GmallCache gmallCache = method.getAnnotation(GmallCache.class);

        //获取注解中的缓存key的前缀
        String prefix = gmallCache.prefix();

        //获取目标方法的参数
        String args = StringUtils.join(joinPoint.getArgs(), ",");

        //组装缓存的key
        String key = prefix + args;

        // 0.为了防止缓存穿透，使用布隆过滤器
        if (!this.bloomFilter.contains(key)) {
            return null;
        }

        //1.查询缓存，如果缓存命中，直接返回
        String json = this.redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(json)) {
            return JSON.parseObject(json, method.getReturnType());
        }

        //2.为了防止缓存击穿，添加分布式锁
        RLock fairLock = this.redissonClient.getFairLock(gmallCache.lock() + args);
        fairLock.lock();
        try {
            //3.再查缓存，如果命中直接返回
            String json2 = this.redisTemplate.opsForValue().get(key);
            if (StringUtils.isNotBlank(json2)) {
                return JSON.parseObject(json2, method.getReturnType());
            }

            //4.执行目标方法
            Object result = joinPoint.proceed(joinPoint.getArgs());

            //5.放入缓存，并释放分布式锁
            int timeout = gmallCache.timeout() + new Random().nextInt(gmallCache.random());
            this.redisTemplate.opsForValue().set(key, JSON.toJSONString(result), timeout, TimeUnit.MINUTES);

            return result;
        } finally {
            fairLock.unlock();
        }
    }

    @Pointcut("execution(* com.atguigu.gmall.index.service.*.*(..))")
    public void pointcut() {
    }

    /**
     * 获取3类信息：
     * 1.获取目标方法参数：joinPoint.getArgs()
     * 2.获取目标方法所在类：joinPoint.getTarget().getClass()
     * 3.获取目标方法：(MethodSignature) joinPoint.getSignature()
     */
    @Before("pointcut()")
    public void before(JoinPoint joinPoint) {
        System.out.println("前置通知方法。。" + Arrays.asList(joinPoint));
    }

    @AfterReturning(value = "pointcut()", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        System.out.println("2返回后通知。。。" + result);
    }

    @AfterThrowing(value = "pointcut()", throwing = "ex")
    public void afterThrowing(Exception ex) {
        System.out.println("3异常后通知。。。" + ex.getMessage());
    }

    @After("pointcut()")
    public void after() {
        System.out.println("4最终通知。。。");
    }

    /**
     * 1.必须返回Object类型参数
     * 2.必须有ProceedingJoinPoint参数
     * 3.必须抛出Throwable异常
     * 4.必须手动执行目标方法
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        System.out.println("5环绕通知 前增强。。。。。");
        Object result = joinPoint.proceed(joinPoint.getArgs());
        System.out.println("6环绕通知 后增强 。。。。");
        return result;
    }

}
